import { BoxGeometry3D, CircleGeometry3D, CylinderGeometry3D, IcosahedronGeometry3D, LatheGeometry3D, Mesh3D, MeshPhongMaterial3D, Node3D, OctahedronGeometry3D, PerspectiveCamera3D, PlaneGeometry3D, RingGeometry3D, Scene3D, SphereGeometry3D, StatsComponent, TetrahedronGeometry3D, TextureLoader3D, TorusGeometry3D, TorusKnotGeometry3D, Vector2, WebGLRenderer3D } from 'feng3d-three';

import { $set } from '@feng3d/serialization';
import mapUrl from '../../resources/textures/uv_grid_opengl.jpg';

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let stats: StatsComponent;

init();
animate();

function init()
{
    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 45, aspect: window.innerWidth / window.innerHeight, near: 1, far: 2000 });
    camera.entity.position.z = 400;

    scene = new Node3D().addComponent('Scene3D');

    let object: Mesh3D;

    const ambientLight = new Node3D().addComponent('AmbientLight3D', { color: { value: 0xcccccc }, intensity: 0.4 });
    scene.entity.addChild(ambientLight.entity);

    const pointLight = new Node3D().addComponent('PointLight3D', { color: { value: 0xffffff }, intensity: 0.8 });
    camera.entity.addChild(pointLight.entity);
    scene.entity.addChild(camera.entity);

    const map = new TextureLoader3D().load(mapUrl);
    map.wrapS = map.wrapT = 'RepeatWrapping';
    map.anisotropy = 16;

    const material = $set(new MeshPhongMaterial3D(), { map, side: 'DoubleSide' });
    //

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new SphereGeometry3D(), { radius: 75, widthSegments: 20, heightSegments: 10 }),
        material
    });
    object.entity.position.set(-300, 0, 200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new IcosahedronGeometry3D(), { radius: 75, detail: 1 }),
        material
    });
    object.entity.position.set(-100, 0, 200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new OctahedronGeometry3D(), { radius: 75, detail: 2 }),
        material
    });
    object.entity.position.set(100, 0, 200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new TetrahedronGeometry3D(), { radius: 75, detail: 0 }),
        material
    });
    object.entity.position.set(300, 0, 200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new PlaneGeometry3D(), { width: 100, height: 100, widthSegments: 4, heightSegments: 4 }),
        material
    });
    object.entity.position.set(-300, 0, 0);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new BoxGeometry3D(), { width: 100, height: 100, depth: 100, widthSegments: 4, heightSegments: 4, depthSegments: 4 }),
        material
    });
    object.entity.position.set(-100, 0, 0);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new CircleGeometry3D(), { radius: 50, segments: 20, thetaStart: 0, thetaLength: Math.PI * 2 }),
        material
    });
    object.entity.position.set(100, 0, 0);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new RingGeometry3D(), { innerRadius: 10, outerRadius: 50, thetaSegments: 20, phiSegments: 5, thetaStart: 0, thetaLength: Math.PI * 2 }),
        material
    });
    object.entity.position.set(300, 0, 0);
    scene.entity.addChild(object.entity);

    //
    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new CylinderGeometry3D(), { radiusTop: 25, radiusBottom: 75, height: 100, radialSegments: 40, heightSegments: 5 }),
        material
    });
    object.entity.position.set(-300, 0, -200);
    scene.entity.addChild(object.entity);

    const points: Vector2[] = [];

    for (let i = 0; i < 50; i++)
    {
        points.push(new Vector2(Math.sin(i * 0.2) * Math.sin(i * 0.1) * 15 + 50, (i - 5) * 2));
    }

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new LatheGeometry3D(), { points, segments: 20 }),
        material
    });
    object.entity.position.set(-100, 0, -200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new TorusGeometry3D(), { radius: 50, tube: 20, radialSegments: 20, tubularSegments: 20 }),
        material
    });
    object.entity.position.set(100, 0, -200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new TorusKnotGeometry3D(), { radius: 50, tube: 10, tubularSegments: 50, radialSegments: 20 }),
        material
    });
    object.entity.position.set(300, 0, -200);
    scene.entity.addChild(object.entity);

    renderer = scene.addComponent('WebGLRenderer3D', {
        parameters: { antialias: true },
        pixelRatio: window.devicePixelRatio,
        size: { x: window.innerWidth, y: window.innerHeight }
    });

    document.body.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');

    document.body.appendChild(stats.dom);

    //

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    const timer = Date.now() * 0.0001;

    camera.entity.position.x = Math.cos(timer) * 800;
    camera.entity.position.z = Math.sin(timer) * 800;

    camera.entity.lookAt(scene.entity.position);

    scene.entity.traverse(function (object: Node3D)
    {
        if (object.isMesh === true)
        {
            object.rotation.x = timer * 5;
            object.rotation.y = timer * 2.5;
        }
    });

    renderer.render(scene, camera);
}
